#include <8051.h>
#define S51

#ifdef S51
 #warning "Compiling for AT89S51..."
 #warning "PORTs not configured for anything other than S51!"

 #endif
/* Version History
Tested on AT89C4051
May 31, 2008 - Added compiling option S51, changed #defines for PORTs on S51 only!!!

May 25, 2008 - Initial breadboard testing complete, TODO: in-system testing
             Known issues: Minor waveform asymmetry due to i2c routines when updating upon receiving packet

pwm.c Functional description
Timer0 operates as watchdog to timeout if an ACK is not received on the serial port
...
...


*/
/*

PWM theory and notes
OHC = overhead code

Since there is some overhead to read in the duty cycle, a possible way to correct this is
to start a timer before the overhead code(OHC) and stop it afterwards, then initialize and preset the loop to
some time t - a, where a is the # ticks of the timer and t is the time if there was no over head code. The expected
result is such that the time that is spent high(after the for loop) is equaled to a time spent low
by the predelay of t - a, effectly the wave form will be symmetric.

*/


//--------Globals-----------------------------------
//RS232 packet structure
//byte pos:  0       1      2       3       4      5        6            
//         ['F'][Ch0Duty][Ch1Duty][(OE)][Sol12V][Sol24V][Checksum]

//[(OE)]    Function 
// 0xAA   activate drivers (nOE = 0)
// 0x55   deactivate drivers (nOE = 1)

//Checksum in the sum of the bytes in pos 1 through 5

unsigned char packet[7];
unsigned char index;
volatile unsigned char frame_started; //used by serial ISR to remember if a frame has started(based on identified char 'F'
volatile unsigned char frame_complete; //use by serial ISR and main() to indicate that a frame is complete and ready for processing
//main() should clear frame complete when it is done processing

unsigned char uart_data;

volatile unsigned char beat_send; //sends beat with the expectation of a response of beat+1 on the serial port

unsigned char timer0_of;


unsigned char ch0duty, ch1duty;

char SCALE_FACTOR;

//---------------------------------------------------

#define ch0 P3_6
#define ch1 P3_7
#define nOE P2_0  //active low controlling the tristate buffer, output to FET gates

//#define LED P0_2
#define LED0 P0_0 //
//#define LED1 P0_4
//#define TIME P0_5

//RS232 commands
#define OE_ACTIVE 0xAA //used in pack[3] to indicate
#define INTERFACE_ACTIVE 0xAF
#define REQ_PACK_ISR 's' //RS232 CMD request port states (occurs from interrupt)
#define REQ_PACK 'r' //same as above but occurs from main loop
#define CMD_BEAT 'F'

#define cycle_ticks 3333

//-------------------------------------------------------




//'-------------------I2C functions-------------------

#define SDA P1_7
#define SCL P1_6

//i2c bits for slave_addr [0][1][1][1][a2][a1][a0][w/r]
unsigned char i2c_read(unsigned char slave_addr);
void i2c_write(unsigned char slave_addr, unsigned char byte);

unsigned char i2c_tx(unsigned char byte);
unsigned char i2c_rx(unsigned char ack);

void i2c_start();
void i2c_stop();
//----------------------------------------------------

//---------Serial Functions ------------------------

//#pragma callee_saves InitSerial

#pragma callee_saves Tx

void InitSerial(); //initialize for serial I/O

void Tx(char b); //send byte via serial port
char Rx(); //receive byte

void InitSerial()
{
       // TR1 = 0;
       //TCON = 0;
        TR1 = 0; //disable timer before configuring mode
        TF1 = 0;//maybe necessary
       
        TI = 0;
        RI = 0;
       
        PCON = 0x80; //double this baudrate (SMOD = 1)
        SCON = 0x50; //select mode, enable reception
        //TH1 = 221; //timer overflow reg
        //TH1 = baud;//115200 bps @ 22.1184MHzx2, 1337
        TH1 = 255; //57600 bps @ 11.0592 (SMOD = 1)     
                   //115200 bps @ 22.1128(SMOD = 1)   
        //TH1 = 182 at 3.120MHz;
        
        TMOD = (TMOD & 0x0F) | 0x20; //set timer #1 for 8 bit auto-reload; lower nible timer0 config'd as 16-bit timer
        
        TR1 = 1; //bit 6 of TCON to enable timer 1;  
        return;
}

void Tx(char b)
{
   TI = 0;
  // SBUF = b;
   _asm mov _SBUF, dpl _endasm;
   while(!TI);
   return;
}

char Rx()
{
   RI = 0;
   while(!RI);
  // RI = 0;
   return SBUF;
}

//-----------------------interrupt serial-------------

void serial_isr(void) interrupt 4
{
     /*
     If the interrupt service routine is defined without using a register bank or with register
bank 0 (using 0), the compiler will save the registers used by itself on the stack upon
entry and restore them at exit, however if such an interrupt service routine calls another
function then the entire register bank will be saved on the stack. This scheme may be
advantageous for small interrupt service routines which have low register usage.
If the interrupt service routine is defined to be using a specific register bank then
only a, b & dptr are save and restored, if such an interrupt service routine calls another
function (using another register bank) then the entire register bank of the called function
will be saved on the stack. This scheme is recommended for larger interrupt service
routines.
Calling other functions from an interrupt service routine is not recommended
*/
     
     if (RI == 1)
     {
            RI = 0; // clear reception flag for next reception 
            uart_data = SBUF; 
           // SBUF = uart_data;  //Send back same data on uart
            
        //    LED = !LED;

            if(!frame_complete)
            {
                if(frame_started) {
                     packet[index] = uart_data;              
                      
                      if(index == 6)
                      {
                        frame_complete = 1; //flag that a frame has completed and ready for processing by main() 
                        frame_started = 0; //reset for next transmission
                        //index = 0;
                      } else {
                       index++;
                      }                              
                } else if(uart_data == 'F') { //start character receceived for update frame
                 //reset watch dog              
                     TR0 = 0;
                     TH0 = 0;
                     TL0 = 0;
                     timer0_of = 0;
                     
                     frame_started = 1;
                     index = 1;
                     packet[0] = uart_data;

                } else if(uart_data == CMD_BEAT+1) { //received a beat response 
                     
                    // beat = uart_data; 
                        
                    //reset watch dog
                     TR0 = 0;
                     TH0 = 0;
                     TL0 = 0;
                     timer0_of = 0;
                     
                    //enable driver
                     nOE = 0;
                } else if(uart_data == 'C') { 
                                      //reset watch dog              
                     TR0 = 0;
                     TH0 = 0;
                     TL0 = 0;
                     timer0_of = 0;
                     
                     frame_started = 1;
                     index = 1;
                     packet[0] = uart_data;
                     
                } else { //unrecognized data
                        
                     nOE = 1;        
                }
            }
      }
       
      TI = 0;//just incase to prevent repetitive interrupting
      
    //  if(TI == 1) TI = 0; //clear transmission flag for next transmission 
     
      //Tx(0xff); 
      return;
}  
//--------timer0 interrupt----------------------------------------------------
void timer0_isr() interrupt 1 //_naked
{
   timer0_of++;
   //__asm reti __endasm;
   // ES = 0;
   beat_send = 1;
 //  LED1 = !LED1;

  /*
   if(timer0_of == 5) {
        //TI = 0;
        //SBUF = CMD_BEAT; 
        beat_send = 1;
        
   } else*/ 
   if(timer0_of == 90) { //uhoh watchdog biting
   
       timer0_of = 0;
       
       
       P2 = 0;
       P0 = P0 & 0x01;
       
       LED0 = !LED0;

       nOE = 1; //disable outputs
       frame_started = 0;
       
      // beat_send = 1; //flag to main loop to send another beat
       
       TI = 0;
       SBUF = REQ_PACK_ISR;
   }

  // ES = 1;
   
  //return;
} 
//--------------------------------------------------------------------

void update();

void main(void)
{
  //   typedef uc unsigned char;
     
      int i;
     
  //   #define SCALE_FACTOR 0x05
     
     ch0duty = 0;
     ch1duty = 0;
     
     P2 = 0;
     P0 = 0;
     
     SCALE_FACTOR = 5;
     
     index = 0;
     frame_started = 0;
     frame_complete = 0;
     nOE = 1;
     
     timer0_of = 0;
     beat_send = 0;
     
     InitSerial();
    // TMOD &= 0xF1; //sset timer0 as 16 bit
   //  REN = 1; //SCON, enable serial reception
     ES = 1; // Enable serial interrupt
     ET0 = 1; //IE.1 timer0 interrupt
     EA = 1; // Enable global interrupt
    
     PS = 1; //set serial interrupt as slightly higher priority, maybe needed since timer0 overflows quickly
     //beat = 'F';//initial beat
     
     TI = 0;
     SBUF = INTERFACE_ACTIVE; //send interface active
      
     while(1)
     {
          //frame_complete = 0;
  
     //read in duty cycle from port P1(assumes multiplexed)
      //OHC start    
          //select = 0; //select first 
          //ch0duty = P1;
          //select = 1; //select  second
          //ch1duty = P1;
          if(beat_send)
          {
              beat_send = 0;
              //TI = 0;
              SBUF = CMD_BEAT; 
          }
          
          TR0 = 1; //enable timer0 for 'pseudo-watchdog'
          
          if(frame_complete) {
            frame_complete = 0;
            update();
          }
                
        //  TR0 = 0; //disable timer 0
          
          if(ch0duty != 255)
             ch0 = 0;
          
          if(ch1duty != 255)
             ch1 = 0;
       //OHC end
          
          for(i = 255*SCALE_FACTOR; i; i--) //complete one cycle
          {
              if(frame_complete) break; //received an update from serial
              
              if(ch0duty*SCALE_FACTOR == i)
                ch0 = 1;
                
              if(ch1duty*SCALE_FACTOR == i)
                ch1 = 1;
                
          }
          
            
     }
}
//--------------------------------------------------------------------
void update()
{
    unsigned char checksum;

//  if(frame_complete)
//{
      //calculate checksum 
      checksum = packet[1] + packet[2] + packet[3] + packet[4] + packet[5];
      
      if(packet[0] == 'C')
      {
                    SCALE_FACTOR = packet[2];
                    nOE = 1;
                    P2 = 0x00;
                    P0 = 0x00;
                    return;
      }
          
      if(checksum != packet[6]) //checksum error
      {
         //frame_flag = 0; 
         
         TI = 0;
         SBUF = 0xA1;
         while(!TI);
         
         TI = 0;
         SBUF = checksum;
         while(!TI);
         
         return;
               
      } else { //valid data received, process
          
          if(packet[3] == OE_ACTIVE) 
                 nOE = 0;// OE is a GO
            else 
                 nOE = 1;
          
          ch0duty = packet[1];
          ch1duty = packet[2];
       #ifndef S51
          i2c_write(0x70, packet[4]); //12V relay IOs
          i2c_write(0x72, packet[5]); //24V relay IOs
          #else
          #define MASK 0x03
          P2 = packet[4];
          P0 = packet[5];
          
       #endif
          //i2c_write(0, ch0duty);

      }
      
     // frame_complete = 0; //flag that the frame has been processed to serial ISR
   //frame_started = 0; this code is unneeded since this is clear the moment a frame is complete in serialISR()
// } 
}

//---------------- I2C functions ---------------------------------------
unsigned char i2c_read(unsigned char slave_addr)
{
   unsigned char byte;
   
   i2c_start();
   i2c_tx(slave_addr | 1); //lower bit indicates read operation
   
   byte = i2c_rx(1); //assert a master ack
   i2c_stop();
   return byte;
}


void i2c_write(unsigned char slave_addr, unsigned char byte)
{
  // unsigned char byte;
   
   i2c_start();
  
   i2c_tx(slave_addr & 0xFE); //lower bit indicates \write operation
   
   i2c_tx(byte);
   
   i2c_stop();
     return;
}
void i2c_start()
{
   //START condition; SDA H to L while SCL = 1
  //Note: this is also a stop condition if SDA is low before SCL = 1!
  //Bus assumed idle when SDA and SCL are high?
    SDA = 1;             // i2c start bit sequence
    SCL = 1;
    SDA = 0;
    SCL = 0;
}

void i2c_stop()
{
    //STOP condition; SDA from L to H while SCL = 1 
    SDA = 0;  
    SCL = 1; 
    
    SDA = 1; 
}

//shift out one byte MSB first
unsigned char i2c_tx(unsigned char byte) 
{   
  unsigned char i;
  static bit ackbit; //static necessary?

  for(i = 0x80; i; i >>= 1)
  {
      if (byte & i) 
        SDA = 1;
      else 
        SDA = 0;

    SCL = 1;
    SCL = 0; //changing of SDA only allowed when SCL = 0
  } 
    
    SDA = 1;
    
    SCL = 1; //slave device now pulling SDA low for acknowledgement
    
    ackbit = SDA;
     
    SCL = 0;
    
    return ackbit;
}

unsigned char i2c_rx(unsigned char ack)
{
     unsigned char byte = 0, i;
     
   //  SCL = 0; //if SCL = 0 is not used in i2c_tx(after a tx, either RX, another tx, or stop is called)
     
     SDA = 1;
     
     for (i = 0x80; i; i >>=1)
     {
       SCL = 1;
       
       while(!SCL);
       
       if (SDA) byte |= i; 
       SCL = 0;  
     }
     
     //Assert SDA(ack from master)
    if(ack) SDA = 0; //assert SDA low while SCL is high?
    else SDA = 1;
    
    SCL = 1; //clock ack bit
    SCL = 0;
    
  //  SDA = 1; //necessary??
     return byte;
}
//--------------------------------------------------------------------
